<?php


namespace App\Services\WebSocket;


use App\Services\Websocket\Rooms\RoomContract;
use Illuminate\Support\Facades\App;
use Swoole\WebSocket\Server;

class WebSocket
{
    use Authenticatable;

    const PUSH_ACTION = 'push';
    const EVENT_CONNECT = 'connect';
    const USER_PREFIX = 'uid_';

    /**
     * Determine if to broadcast.
     *
     * @var boolean
     */
    protected $isBroadcast = false;
    /**
     * Scoket sender's fd.
     *
     * @var integer
     */
    protected $sender;

    /**
     * Recepient's fd or room name.
     *
     * @var array
     */
    protected $to = [];

    /**
     * Websocket event callbacks.
     *
     * @var array
     */
    protected $callbacks = [];


    /**
     * Room adapter.
     *
     * @var RoomContract
     */
    protected $room;


    /**
     * DI Container.
     *
     * @var \Illuminate\Contracts\Container\Container
     */
    protected $container;

    /**
     * WebSocket constructor.
     * @param RoomContract $room
     */
    public function __construct(RoomContract $room)
    {
        $this->room = $room;
    }

    public function broadcast():self {
        $this->isBroadcast= true;
        return $this;
    }

    public function to($values):self {
        $values = is_string($values) || is_integer($values) ? func_get_args() : $values;
        foreach ($values as $value) {
            if (!in_array($values,$this->to)){
                $this->to[] =$value;
            }
        }

        return  $this;
    }

    public function join($rooms):self {
        $rooms = is_string($rooms) || is_integer($rooms) ? func_get_args() : $rooms;
        $this->room->add($this->sender,$rooms);
        return  $this;
    }

    public function leave($rooms=[]):self {
        $rooms = is_string($rooms) || is_integer($rooms) ? func_get_args() : $rooms;
        $this->room->delete($this->sender,$rooms);
        return  $this;
    }

    public function emit(string $event,$data):bool{
        $fds = $this->getFds();
        $assigned=!empty($this->to);
        if (empty($fds) && $assigned){
            return  false;
        }
        $payload = [
            'sender' => $this->sender,
            'fds' => $fds,
            'broadcast' => $this->isBroadcast,
            'assigned' =>$assigned,
            'event' =>$event,
            'message' =>$data
        ];
        $server =app('swoole');
        $pusher = Pusher::make($payload,$server);
        $parser = app('swoole.parser');
        $pusher->push($parser->encode($pusher->getEvent(),$pusher->getMessage()));
        $this->reset();
        return true;
    }

    public function in($room){
        $this->join($room);
        return $this;
    }

    public function on(string $event,$callback){
        if (!is_string($callback) && !is_callable($callback)){
            throw new \InvalidArgumentException("Invalid websocket callback");
        }
        $this->callbacks[$event] =$callback;
        return $this;
    }

    public function eventExists(string  $event){
        return array_key_exists($event,$this->callbacks);
    }

    public function call(string $event ,$data=null){
        if (!$this->eventExists($event)){
            return null;
        }
        $isConnect = $event === static::EVENT_CONNECT;
        $dataKey = $isConnect ? 'request' :'data';
        return App::call($this->callbacks[$event],[
            'websocket' => $this,
             $dataKey =>$data
        ]);
    }

    public function setSender(int $fd){
        $this->sender = $fd;
        return $this;
    }
    public function getSender(){
        return $this->sender;
    }

    public function getIsBroadcast(){
        return $this->isBroadcast;
    }

    public function getTo(){
        return $this->to;
    }

    protected function getFds(){
        $fds = array_filter($this->to,function ($value){
            return is_integer($value);
        });
        $rooms =array_diff($this->to,$fds);
        foreach ($rooms as $room){
            $clients = $this->room->getClients($room);
            if (empty($clients) && is_numeric($room)){
                $fds[] = $room;
            }else {
                $fds =array_merge($fds,$clients);
            }
        }
        return array_values(array_unique($fds));
    }

    public function reset($force = false)
    {
        $this->isBroadcast = false;
        $this->to = [];

        if ($force) {
            $this->sender = null;
            $this->userId = null;
        }

        return $this;
    }
}
